/***************************************************************************
 *   Copyright (C) 2009 by maarten,,,   *
 *   maarten@maarten-laptop   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "objects/plane.h"
#include "objects/vertex.h"
#include "objects/normal.h"
#include "objects/objecttexture.h"
#include "cgpanel/raytracer/ray.h"
#include "cgpanel/raytracer/box.h"
#include "cgpanel/raytracer/hitrecord.h"
#include <algorithm>
#include <iostream>
using namespace std;


namespace object {

Plane::Plane()
{
  surfaceNormal = NULL;
  m_normalsGiven = false;
  m_mat = NULL;
  specificNormal = NULL;
  bbox = new draw::Box();
}

Plane::Plane(Vertex *va, Vertex *vb, Vertex *vc)
{
  surfaceNormal = NULL;
  m_normalsGiven = false;
  m_mat = NULL;
  m_tex = NULL;
  specificNormal = NULL;
  bbox = NULL;
  
  addCorner(va, NULL, NULL);
  addCorner(vb, NULL, NULL);
  addCorner(vc, NULL, NULL);
  

}

void Plane::setBoundingBox()
{
  float xmin = m_vertexList[0]->x();
  xmin = min(xmin, m_vertexList[1]->x());
  xmin = min(xmin, m_vertexList[2]->x());
  
  float ymin = m_vertexList[0]->y();
  ymin = min(ymin, m_vertexList[1]->y());
  ymin = min(ymin, m_vertexList[2]->y());
  
  float zmin = m_vertexList[0]->z();
  zmin = min(zmin, m_vertexList[1]->z());
  zmin = min(zmin, m_vertexList[2]->z());
  
  float xmax = m_vertexList[0]->x();
  xmax = max(xmax, m_vertexList[1]->x());
  xmax = max(xmax, m_vertexList[2]->x());
  
  float ymax = m_vertexList[0]->y();
  ymax = max(ymax, m_vertexList[1]->y());
  ymax = max(ymax, m_vertexList[2]->y());
  
  float zmax = m_vertexList[0]->z();
  zmax = max(zmax, m_vertexList[1]->z());
  zmax = max(zmax, m_vertexList[2]->z());
  bbox = new draw::Box();
  bbox->setValues(xmin, ymin, zmin, xmax, ymax, zmax);
  
  xCenter = (xmin + xmax)/(float)2;
  yCenter = (ymin + ymax)/(float)2;
  zCenter = (zmin + zmax)/(float)2;
  
  // the things we can calculate in advance
  Aa = getVertex(0)->x() - getVertex(1)->x();
  Ad = getVertex(0)->x() - getVertex(2)->x(); 
  Ab = getVertex(0)->y() - getVertex(1)->y();
  Ae = getVertex(0)->y() - getVertex(2)->y();  
  Ac = getVertex(0)->z() - getVertex(1)->z();
  Af = getVertex(0)->z() - getVertex(2)->z();  
}


Plane::~Plane()
{
  if(surfaceNormal!=NULL)
    delete surfaceNormal;
}

void Plane::addCorner(Vertex *vertex, ObjectTexture *texture, Normal *normal)
{
    m_vertexList.push_back(vertex);
    m_textureList.push_back(texture);
    
    if(normal != NULL)
        m_normalsGiven = true;
    
    m_normalList.push_back(normal);
    
    if(m_normalList.size() == 3){
      calculateNormal();
      
    }
    if(m_vertexList.size() == 3)
      setBoundingBox();
    
}

void Plane::calculateNormal()
{
  if(m_normalsGiven){
      float normX =
(m_normalList[0]->x()+m_normalList[1]->x()+m_normalList[2]->x())/(float)3;

      float normY =
(m_normalList[0]->y()+m_normalList[1]->y()+m_normalList[2]->y())/(float)3;

      float normZ =
(m_normalList[0]->z()+m_normalList[1]->z()+m_normalList[2]->z())/(float)3;
    
      surfaceNormal = new Vertex(normX,normY,normZ,0);
      surfaceNormal->normalize();
    
    
  }else{
      Vertex *bmina = m_vertexList[1]->minus(m_vertexList[0]);
      Vertex *cmina = m_vertexList[2]->minus(m_vertexList[0]);
     
      surfaceNormal = bmina->vecProd(cmina);
      surfaceNormal->setH(0);
      surfaceNormal->normalize();
      
      delete bmina;
      delete cmina;
  }

}

Vertex* Plane::getSurfaceNormal(float b, float g)
{
  if(g == -1.0 || !m_normalsGiven)
    return surfaceNormal;
  else
  {
    float a = 1.0 - b - g;
    if(specificNormal != NULL)// everytime used this way, a delete is necessary
      delete specificNormal;
    
    float normX =
(a*m_normalList[0]->x()+b*m_normalList[1]->x()+g*m_normalList[2]->x());

    float normY =
(a*m_normalList[0]->y()+b*m_normalList[1]->y()+g*m_normalList[2]->y());

    float normZ =
(a*m_normalList[0]->z()+b*m_normalList[1]->z()+g*m_normalList[2]->z());
    
    specificNormal = new Vertex(normX,normY,normZ,0);
    specificNormal->normalize();
      
    return specificNormal; 
    
  }
}

// implicit plane equation
float Plane::planeEquation(object::Vertex * p)
{
  // implement
  Vertex * temp1 = p->minus(m_vertexList[0]);
  Vertex * temp2 = m_vertexList[1]->minus(m_vertexList[0]);
  Vertex * temp3 = m_vertexList[2]->minus(m_vertexList[0]);
  
  float ret = temp1->dotProd(temp2->vecProd(temp3));
  delete temp1;
  delete temp2;
  delete temp3;
  
  return ret;
}

bool Plane::hit(draw::Ray * ray, draw::HitRecord ** rec)
{
    float lastGamma;
    float lastBeta;
    
    // calculate 3X3 matrix (matrix not used for speeding up)
    float Ag = ray->direction()->x();
    float Ah = ray->direction()->y();   
    float Ai = ray->direction()->z();

    // calculate 3X1 matrix
    float Bj = getVertex(0)->x() -ray->origin()->x();
    float Bk = getVertex(0)->y() -ray->origin()->y();
    float Bl = getVertex(0)->z() -ray->origin()->z();

    // precalculations
    float ei_minus_hf = Ae * Ai - Ah *  Af;
    float gf_minus_di = Ag * Af - Ad *  Ai;
    float dh_minus_eg = Ad * Ah - Ae *  Ag;
    float ak_minus_jb = Aa * Bk - Bj *  Ab;
    float jc_minus_al = Bj * Ac - Aa *  Bl;
    float bl_minus_kc = Ab * Bl - Bk *  Ac;

    // calculate M
    float M =   Aa * ei_minus_hf + Ab * gf_minus_di + Ac * dh_minus_eg;

    /*******************************************************
        T calculations
    ********************************************************/
    float t = -(Af*ak_minus_jb + Ae *jc_minus_al + Ad*bl_minus_kc) / M;

    if(t < 0.1 || t >1000000000){
      return false;
    }

    /*******************************************************
        Gamma calculations
    ********************************************************/
    lastGamma = ( 
                   Ai*ak_minus_jb + Ah * jc_minus_al +        
                    Ag*bl_minus_kc 
                 )/M;
    if(lastGamma < 0 || lastGamma > 1){
        return false;
    }
    /*******************************************************
        Beta calculations
    ********************************************************/
    lastBeta = ( 
                    Bj*ei_minus_hf +
                    Bk*gf_minus_di +        
                    Bl*dh_minus_eg 
                 )/M;

    if( (lastBeta < 0) || (lastBeta > (1-lastGamma))){ 
      return false;
    }
    
    //lastAlpha = 1 - lastBeta - lastGamma;

    //Intersection found!
    //(*rec)->setAlpha(lastAlpha);
    (*rec)->setBeta(lastBeta);
    (*rec)->setGamma(lastGamma);
    (*rec)->setT(t);
    (*rec)->setPlane(this);
    return true;
}

bool Plane::hit(draw::Ray * ray, float tMax)
{
    float lastGamma;
    float lastBeta;

    // calculate 3X3 matrix (matrix not used for speeding up)
    
    float Ag = ray->direction()->x();
    float Ah = ray->direction()->y();
    float Ai = ray->direction()->z();

    // calculate 3X1 matrix
    float Bj = getVertex(0)->x() -ray->origin()->x();
    float Bk = getVertex(0)->y() -ray->origin()->y();
    float Bl = getVertex(0)->z() -ray->origin()->z();

    // precalculations
    float ei_minus_hf = Ae * Ai - Ah *  Af;
    float gf_minus_di = Ag * Af - Ad *  Ai;
    float dh_minus_eg = Ad * Ah - Ae *  Ag;
    float ak_minus_jb = Aa * Bk - Bj *  Ab;
    float jc_minus_al = Bj * Ac - Aa *  Bl;
    float bl_minus_kc = Ab * Bl - Bk *  Ac;

    // calculate M
    float M =   Aa * ei_minus_hf + Ab * gf_minus_di + Ac * dh_minus_eg;

    /*******************************************************
        T calculations
    ********************************************************/
    float t = -(Af*ak_minus_jb + Ae *jc_minus_al + Ad*bl_minus_kc) / M;

    if(t < 0.1 || t >tMax /*TODO*/){
      return false;
    }

    /*******************************************************
        Gamma calculations
    ********************************************************/
    lastGamma = ( 
                   Ai*ak_minus_jb + Ah * jc_minus_al +        
                    Ag*bl_minus_kc 
                 )/M;
    if(lastGamma < 0 || lastGamma > 1){
        return false;
    }
    /*******************************************************
        Beta calculations
    ********************************************************/
    lastBeta = ( 
                    Bj*ei_minus_hf +
                    Bk*gf_minus_di +        
                    Bl*dh_minus_eg 
                 )/M;

    if( (lastBeta < 0) || (lastBeta > (1-lastGamma))){
      return false;
    }

    return true;
}

void Plane::print()
{
    qDebug("Plane:");
    for(int i = 0; i < m_vertexList.size(); ++i)
        m_vertexList[i]->print();
}

}
